#include <gtest/gtest.h>

#include "helper/ControlFlow.h"

using namespace helper;


template<typename B, typename R>
R tryFold(const B & init, const std::function<R(const B &, const int &)> & f) {
  B b = init;
  for(int i = 1; i < 4; ++i){
    R && r = f(b, i);
    if(r.isBreak()){
      return r;
    }
    else {
      b = r.continueValue().value();
      continue;
    }
  }
  return R::Continue(b);
}





TEST(ControlFlowFlagTest, testFlow){
  // any:  i > 0 => break; 
  ASSERT_TRUE(
    (tryFold<ControlFlowOpCode, ControlFlowFlag>(ControlFlowOpCode::CONTINUE
      , [](const auto & init, const int & i) {
        if(i  > 0) 
          return ControlFlowFlag::Break();
        return ControlFlowFlag::Continue();
      }
    )).isBreak()
  );

  // all:  i > 0 => continue
  ASSERT_TRUE(
    (tryFold<ControlFlowOpCode, ControlFlowFlag>(ControlFlowOpCode::CONTINUE
      , [](const auto & init, const int & i) {
        if(i  > 0) 
          return ControlFlowFlag::Continue();
        return ControlFlowFlag::Break();
      }
    )).isContinue()
  );
}





TEST(ControlFlowTest, testBreakBase){
  auto && cf = ControlFlow<>::Break(1);
  ASSERT_TRUE(cf.isBreak());
  ASSERT_FALSE(cf.isContinue());
  ASSERT_EQ(std::optional<int>(1), cf.breakValue());
  ASSERT_EQ(std::nullopt, cf.continueValue());
}

TEST(ControlFlowTest, testContinueBase){
  auto && cf = ControlFlow<>::Continue(2);
  ASSERT_TRUE(cf.isContinue());
  ASSERT_FALSE(cf.isBreak());
  ASSERT_EQ(std::optional<int>(2), cf.continueValue());
  ASSERT_EQ(std::nullopt, cf.breakValue());
}

TEST(ControlFlowTest, testString){
  { // Break;
    auto && cf = ControlFlow<std::string>::Break("hello");
    ASSERT_TRUE(cf.isBreak());
    ASSERT_FALSE(cf.isContinue());
    ASSERT_EQ(std::optional<std::string>("hello"), cf.breakValue());
    ASSERT_EQ(std::nullopt, cf.continueValue());
  }
  { // Continue
    auto && cf = ControlFlow<int, std::string>::Continue("2");
    ASSERT_TRUE(cf.isContinue());
    ASSERT_FALSE(cf.isBreak());
    ASSERT_EQ(std::optional<std::string>("2"), cf.continueValue());
    ASSERT_EQ(std::nullopt, cf.breakValue());
  }
}


TEST(ControlFlowTest, testFlow){
  auto f = [](const long long & init, const int & i) -> ControlFlow<long long, long long> {
      if(i  > 0) 
        return ControlFlow<long long, long long>::Break(i);
      return ControlFlow<long long, long long>::Continue(i);
  };
  // any:  i > 0 => break; 
  ASSERT_EQ(1ll, (tryFold<long long, ControlFlow<long long, long long> >(0ll, f)).breakValue().value());

  
  // all:  i > 0 => continue
  ASSERT_EQ(3,  
    (tryFold<int, ControlFlow<int, int>>(0, [](const int & init, const int & i) -> ControlFlow<int, int> {
      if(i  > 0) 
        return ControlFlow<int, int>::Continue(i);
      return ControlFlow<int, int>::Break(i);
    })).continueValue().value()
  );
}
